package com.bank.util;

import com.alibaba.druid.pool.DruidDataSource;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.Properties;

/**
 * JDBC工具类1.0
 *  使用jdbc书写添加 修改 删除 查询后
 *  我们发现 无论怎么操作 加载驱动 获取连接都相同 连接需要关闭
 *  而且加载驱动仅需要执行1次 所以可以将加载驱动 在工具类中
 *  获取连接 输入的参数同样书写在工具类中 这样就不需要重复的输入
 *  ----------------------------------------------------------
 *  JDBC工具类2.0
 *  在1.0中将配置数据抽离为属性 但是这些属性还是定义在代码中
 *  如果修改 还需要重新编译 在未来的开发中 使用配置文件自动读取来解决这个问题
 *  类似于之前学习的log4j
 *  所以2.0版本 我们将配置文件继续抽离 存入.properties结尾的文件中
 *  通过自动读取 完成工具类的使用
 *  ----------------------------------------------------------
 *  JDBC工具类3.0
 *  在2.0中每次获取连接都是重新创建连接，在项目中需要频繁的创建销毁连接对象，加大了内存的损耗
 *  可以通过连接池解决这一问题
 *  将读取配置文件的内容 创建连接池进行使用 使用连接池管理连接 减少损耗
 *  -----------------------------------------------------------
 *  JDBC工具类4.0
 *  在3.0基本已经解决了资源损耗问题，将来连接交给连接池管理 但是工具类的功能仍然没有完善
 *  例如在之前学习与代码书写中 我们发现添加 修改 删除 的代码仅sql语句与传入的参数不同
 *  所以可以将其封装为方法
 *  1)单条dml语句执行方法(无需考虑事务)
 *  2)单条dml语句执行方法(需考虑事务)
 *  ---------------------------------------------------------------
 *  JDBC工具类5.0
 *  4.0中将添加 修改 删除功能封装成方法 是因为修改的内容不多
 *  对数据库操作 仅剩查询 基于ORM对象关系映射
 *  查询的结果应该封装至对应的集合中
 *  1)dql语句执行
 *  2)处理结果集在集合中返回
 */
public class MyJDBCUtil {
    //将参数声明全局变量进行定义
//    private static String driver;
//    private static String url;
//    private static String username;
//    private static String password;
    //使用Druid连接池加载 无需声明这些属性
    private static DruidDataSource ds;//声明连接池属性

    //保证驱动仅加载一次 将其书写在静态代码块
    static{
        //在加载驱动前 先读取配置文件 将对应的属性赋值
        InputStream is = MyJDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties p=new Properties();
        try {
            p.load(is);
            //创建Druid连接池对象 将配置文件数据存入
            ds = new DruidDataSource();
            //通过set方法赋值
            ds.setUrl(p.getProperty("jdbc.url"));
            ds.setDriverClassName(p.getProperty("jdbc.driver"));
            ds.setUsername(p.getProperty("jdbc.username"));
            ds.setPassword(p.getProperty("jdbc.password"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //将获取连接方法 书写为静态方法
    public static Connection getCon(){
        try {
            // return DriverManager.getConnection(url,username,password);
            //使用连接池方法获取连接并返回
            return  ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    public static void  close (ResultSet rs,Statement statement,Connection con){
        try {
            if(rs!=null){
                rs.close();
            }
            if (statement!=null){
                statement.close();
            }
            if (con!=null){
                con.close();
            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }


    /**
     *
     * 单条dml语句执行方法
     * 就是将sql语句执行的代码封装在方法中
     * @param sql 就是需要执行的sql语句 参数使用?占位符替换
     * @param args 就是sql中占位符对应需要传入的数据 与?个数对应
     * @return 执行影响数据库的条数 -1代表执行失败
     */
    public static int dml(String sql,Object ...args){
        Connection con = getCon();
        PreparedStatement ps=null;
        try {
            ps= con.prepareStatement(sql);
            //遍历参数数据
            for (int i = 0; i <args.length ; i++) {
                ps.setObject(i+1, args[i]);
            }
            return ps.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MyJDBCUtil.close(null,ps,con);
        }
        return -1;
    }

    /**
     * dml执行 考虑事务
     * 事务执行 需要开启事务 多个执行sql语句 需要在同一个连接中执行
     * @param con
     * @param sql
     * @param args
     * @return
     */
    public static int dml(Connection con,String sql,Object ...args) throws SQLException {
        //开启事务
        con.setAutoCommit(false);
        PreparedStatement ps = con.prepareStatement(sql);
        //遍历参数数据
        for (int i = 0; i <args.length ; i++) {
            ps.setObject(i+1, args[i]);
        }
        int i = ps.executeUpdate();
        ps.close();
        return i;
    }


    /**
     * dql语句 执行查询
     * @param c ORM对象关系映射的类的class对象 通过该对象对泛型赋值
     * @param sql
     * @param args
     * @return 返回查询结果
     */
    public static <E> ArrayList<E> dql(Class<E> c,String sql,Object... args){
        ArrayList<E> list=new ArrayList<>();
        Connection con = getCon();
        PreparedStatement ps=null;
        ResultSet rs=null;

        try {
            ps = con.prepareStatement(sql);
            for (int i = 0; i <args.length ; i++) {
                ps.setObject(i+1,args[i]);
            }
            rs= ps.executeQuery();
            while(rs.next()){
                //每行数据都会映射为一个对应的java对象
                //使用反射 获取无参构造方法创建对象
                Constructor<E> constructor = c.getConstructor();
                E e = constructor.newInstance();

                //通过反射获取当期类所有声明的属性
                Field[] AllField = c.getDeclaredFields();
                for (Field field : AllField) {
                    String fieldName=field.getName();
                    Object fieldValue=rs.getObject(fieldName);
                    field.setAccessible(true);
                    field.set(e,fieldValue);
                }
                //存入集合
                list.add(e);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            close(rs,ps,con);
        }
        return list;
    }
}

